<?php
declare(strict_types=1);

namespace ExpressionEngine\VariableEngine;

use Contract\Exceptions\LogicException;
use Contract\Exceptions\ValidationException;
use ExpressionEngine\LexicalAnalysisEngine\Enum\LexicalAnalysisEnum;
use ExpressionEngine\LexicalAnalysisEngine\LexicalAnalysisTag;
use ExpressionEngine\VariableEngine\Enum\VariableEnum;

class VariableCompile
{
    protected LexicalAnalysisTag $lexicalAnalysisTag;

    public function __construct(LexicalAnalysisTag $lexicalAnalysisTag)
    {
        $this->lexicalAnalysisTag = $lexicalAnalysisTag;
    }

    /**
     * @param array $variableTree
     * @param array $context
     * @param array $variableList
     * @return mixed
     * @throws LogicException
     * @throws ValidationException
     */
    public function exec(array $variableTree, array $context, array $variableList): mixed
    {
        if (empty($context)) {
            throw new ValidationException('context is empty');
        }
        $contextData = $context;
        foreach ($variableTree as $node) {
            $contextData = match ($node[0]) {
                VariableEnum::NODE_KEY => $this->keyExec($node[1], $contextData, $variableList),
                VariableEnum::NODE_SEARCH => $this->searchExec($node[1], $node[2], $contextData, $variableList),
                default => throw new LogicException('node type:' . $node[0] . ' is not valid'),
            };
        }
        return $contextData;
    }

    /**
     * @param string $key
     * @param array $contextData
     * @return mixed
     * @throws LogicException
     */
    protected function keyExec(string $key, mixed $contextData, array $variableList): mixed
    {
        $key = $this->mapping($key, $variableList);
        if (!is_array($contextData)) {
            throw new LogicException('context is not array');
        }
        if (!array_key_exists($key, $contextData)) {
            throw new LogicException('context do not include key:' . $key);
        }
        return $contextData[$key];
    }

    /**
     * @param string $key
     * @param string $value
     * @param mixed $contextData
     * @return mixed
     * @throws LogicException
     */
    protected function searchExec(string $key, string $value, mixed $contextData, array $variableList): mixed
    {
        $key = $this->mapping($key, $variableList);
        $value = $this->mapping($value, $variableList);
        if (!is_array($contextData)) {
            throw new LogicException('context is not array');
        }
        foreach ($contextData as $contextItem) {
            if (!is_array($contextItem)) {
                continue;
            }
            if (!array_key_exists($key, $contextItem)) {
                continue;
            }
            if ($contextItem[$key] != $value) {
                continue;
            }
            return $contextItem;
        }
        throw new LogicException(sprintf('search:[%s=%s] is not exist', $key, $value));
    }

    /**
     * @param string $word
     * @param array $variableList
     * @return mixed
     * @throws LogicException
     */
    public function mapping(string $word, array $variableList): mixed
    {
        if ($this->lexicalAnalysisTag->isTag($word)) {
            $tagData = $this->lexicalAnalysisTag->parseTag($word);
            if ($tagData['tag_id'] != LexicalAnalysisEnum::TAG_ID_VARIABLE) {
                throw new LogicException('variable:' . $word . ' is not valid');
            }
            if (!array_key_exists($tagData['index'], $variableList)) {
                throw new LogicException('tag index:' . $tagData['index'] . ' is not exist');
            }
            return $variableList[$tagData['index']];
        }
        return $word;
    }

}